/*
 * brin_pageops.c
 *		BRIN 索引的页面处理例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/access/brin/brin_pageops.c
 */
#include "postgres.h"

#include "access/brin_page.h"
#include "access/brin_pageops.h"
#include "access/brin_revmap.h"
#include "access/brin_xlog.h"
#include "access/xloginsert.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/freespace.h"
#include "storage/lmgr.h"
#include "storage/smgr.h"
#include "utils/rel.h"

/*
 * BRIN_PAGETYPE_REGULAR 页面中条目的最大大小。与其他索引 AM 不同，我们可以容忍每页一个条目。
 */
#define BrinMaxItemSize \
	MAXALIGN_DOWN(BLCKSZ - \
				  (MAXALIGN(SizeOfPageHeaderData + \
							sizeof(ItemIdData)) + \
				   MAXALIGN(sizeof(BrinSpecialSpace))))

static Buffer fc_brin_getinsertbuffer(Relation fc_irel, Buffer fc_oldbuf, Size fc_itemsz,
								   bool *fc_extended);
static Size fc_br_page_get_freespace(Page fc_page);
static void fc_brin_initialize_empty_new_buffer(Relation fc_idxrel, Buffer fc_buffer);


/*
 * 将位于旧缓冲区 oldbuf 的偏移量 oldoff 的更新元组 origtup（大小 origsz）更新为新元组 newtup（大小 newsz），作为从 heapBlk 开始的页面范围的摘要元组。 oldbuf 在入口时必须未锁定，退出时也未锁定。
 *
 * 如果 samepage 为真，尝试将新元组放入同一页面，但如果没有空间，则使用其他页面。
 *
 * 如果更新成功，返回 true；revmap 被更新以指向新元组。如果由于某种原因未完成更新，返回 false。如果发生这种情况，调用者可以重试更新。
 */
bool brin_doupdate(Relation fc_idxrel, BlockNumber fc_pagesPerRange,
			  BrinRevmap *fc_revmap, BlockNumber fc_heapBlk,
			  Buffer fc_oldbuf, OffsetNumber fc_oldoff,
			  const BrinTuple *fc_origtup, Size fc_origsz,
			  const BrinTuple *fc_newtup, Size fc_newsz,
			  bool fc_samepage)
{
	Page		fc_oldpage;
	ItemId		fc_oldlp;
	BrinTuple  *fc_oldtup;
	Size		fc_oldsz;
	Buffer		fc_newbuf;
	BlockNumber fc_newblk = InvalidBlockNumber;
	bool		fc_extended;

	Assert(fc_newsz == MAXALIGN(fc_newsz));

	/* 如果条目超大，就别费事了。 */
	if (fc_newsz > BrinMaxItemSize)
	{
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("index row size %zu exceeds maximum %zu for index \"%s\"",
						fc_newsz, BrinMaxItemSize, RelationGetRelationName(fc_idxrel))));
		return false;			/* 保持编译器安静 */
	}

	/* 确保 revmap 足够长以容纳所需的条目 */
	brinRevmapExtend(fc_revmap, fc_heapBlk);

	if (!fc_samepage)
	{
		/* 需要一个页面来放置该条目 */
		fc_newbuf = fc_brin_getinsertbuffer(fc_idxrel, fc_oldbuf, fc_newsz, &fc_extended);
		if (!BufferIsValid(fc_newbuf))
		{
			Assert(!fc_extended);
			return false;
		}

		/*
		 * 注意：返回的新缓冲区可能（尽管不太可能）与旧缓冲区相同，
		 * 如果 brin_getinsertbuffer 确定旧缓冲区确实有足够的空间。
		 */
		if (fc_newbuf == fc_oldbuf)
		{
			Assert(!fc_extended);
			fc_newbuf = InvalidBuffer;
		}
		else
			fc_newblk = BufferGetBlockNumber(fc_newbuf);
	}
	else
	{
		LockBuffer(fc_oldbuf, BUFFER_LOCK_EXCLUSIVE);
		fc_newbuf = InvalidBuffer;
		fc_extended = false;
	}
	fc_oldpage = BufferGetPage(fc_oldbuf);
	fc_oldlp = PageGetItemId(fc_oldpage, fc_oldoff);

	/*
	 * 检查旧元组是否没有被并发更新：它可能完全移动到了其他地方，实际上整页可能已经变成了 revmap 页面。注意，在此处检查的前两个情况中，我们刚刚计算的 "oldlp" 是垃圾；但 PageGetItemId() 足够简单，因此无论如何进行此计算是安全的。
	 */
	if (!BRIN_IS_REGULAR_PAGE(fc_oldpage) ||
		fc_oldoff > PageGetMaxOffsetNumber(fc_oldpage) ||
		!ItemIdIsNormal(fc_oldlp))
	{
		LockBuffer(fc_oldbuf, BUFFER_LOCK_UNLOCK);

		/*
		 * 如果发生这种情况，并且新缓冲区是通过扩展关系获得的，那么我们需要确保不留下未初始化的缓冲区或忘记它。
		 */
		if (BufferIsValid(fc_newbuf))
		{
			if (fc_extended)
				fc_brin_initialize_empty_new_buffer(fc_idxrel, fc_newbuf);
			UnlockReleaseBuffer(fc_newbuf);
			if (fc_extended)
				FreeSpaceMapVacuumRange(fc_idxrel, fc_newblk, fc_newblk + 1);
		}
		return false;
	}

	fc_oldsz = ItemIdGetLength(fc_oldlp);
	fc_oldtup = (BrinTuple *) PageGetItem(fc_oldpage, fc_oldlp);

	/*
	 * ... 或者它可能已经就地更新为不同的内容。
	 */
	if (!brin_tuples_equal(fc_oldtup, fc_oldsz, fc_origtup, fc_origsz))
	{
		LockBuffer(fc_oldbuf, BUFFER_LOCK_UNLOCK);
		if (BufferIsValid(fc_newbuf))
		{
			/* 如上所述，如果我们得到了一个新页面，则初始化并记录 */
			if (fc_extended)
				fc_brin_initialize_empty_new_buffer(fc_idxrel, fc_newbuf);
			UnlockReleaseBuffer(fc_newbuf);
			if (fc_extended)
				FreeSpaceMapVacuumRange(fc_idxrel, fc_newblk, fc_newblk + 1);
		}
		return false;
	}

	/*
	 * 太好了，旧元组完好无损。我们可以继续进行更新。
	 *
	 * 如果旧页面中有足够的空间放置新元组，则进行替换。
	 *
	 * 注意，即使调用者告诉我们没有空间，页面上可能现在有足够的空间，因为并发更新将其他元组移走或用更小的元组替换了一个元组。
	 */
	if (((BrinPageFlags(fc_oldpage) & BRIN_EVACUATE_PAGE) == 0) &&
		brin_can_do_samepage_update(fc_oldbuf, fc_origsz, fc_newsz))
	{
		START_CRIT_SECTION();
		if (!PageIndexTupleOverwrite(fc_oldpage, fc_oldoff, (Item) unconstify(BrinTuple *, fc_newtup), fc_newsz))
			elog(ERROR, "failed to replace BRIN tuple");
		MarkBufferDirty(fc_oldbuf);

		/* XLOG 相关内容 */
		if (RelationNeedsWAL(fc_idxrel))
		{
			xl_brin_samepage_update fc_xlrec;
			XLogRecPtr	fc_recptr;
			uint8		fc_info = XLOG_BRIN_SAMEPAGE_UPDATE;

			fc_xlrec.offnum = fc_oldoff;

			XLogBeginInsert();
			XLogRegisterData((char *) &fc_xlrec, SizeOfBrinSamepageUpdate);

			XLogRegisterBuffer(0, fc_oldbuf, REGBUF_STANDARD);
			XLogRegisterBufData(0, (char *) unconstify(BrinTuple *, fc_newtup), fc_newsz);

			fc_recptr = XLogInsert(RM_BRIN_ID, fc_info);

			PageSetLSN(fc_oldpage, fc_recptr);
		}

		END_CRIT_SECTION();

		LockBuffer(fc_oldbuf, BUFFER_LOCK_UNLOCK);

		if (BufferIsValid(fc_newbuf))
		{
			/* 如上所述，如果我们得到了一个新页面，则初始化并记录 */
			if (fc_extended)
				fc_brin_initialize_empty_new_buffer(fc_idxrel, fc_newbuf);
			UnlockReleaseBuffer(fc_newbuf);
			if (fc_extended)
				FreeSpaceMapVacuumRange(fc_idxrel, fc_newblk, fc_newblk + 1);
		}

		return true;
	}
	else if (fc_newbuf == InvalidBuffer)
	{
		/*
		 * 空间不足，但调用者说有。告诉他们重新开始。
		 */
		LockBuffer(fc_oldbuf, BUFFER_LOCK_UNLOCK);
		return false;
	}
	else
	{
		/*
		 * 旧页面上没有足够的空闲空间。将新元组放在新页面上，并更新 revmap。
		 */
		Page		fc_newpage = BufferGetPage(fc_newbuf);
		Buffer		fc_revmapbuf;
		ItemPointerData fc_newtid;
		OffsetNumber fc_newoff;
		Size		fc_freespace = 0;

		fc_revmapbuf = brinLockRevmapPageForUpdate(fc_revmap, fc_heapBlk);

		START_CRIT_SECTION();

		/*
		 * 如果是新获得的页面，我们需要初始化它。注意，我们将在更新过程中记录初始化，因此这里不需要再做。
		 */
		if (fc_extended)
			brin_page_init(fc_newpage, BRIN_PAGETYPE_REGULAR);

		PageIndexTupleDeleteNoCompact(fc_oldpage, fc_oldoff);
		fc_newoff = PageAddItem(fc_newpage, (Item) unconstify(BrinTuple *, fc_newtup), fc_newsz,
							 InvalidOffsetNumber, false, false);
		if (fc_newoff == InvalidOffsetNumber)
			elog(ERROR, "failed to add BRIN tuple to new page");
		MarkBufferDirty(fc_oldbuf);
		MarkBufferDirty(fc_newbuf);

		/* 需要更新下面的 FSM */
		if (fc_extended)
			fc_freespace = fc_br_page_get_freespace(fc_newpage);

		ItemPointerSet(&fc_newtid, fc_newblk, fc_newoff);
		brinSetHeapBlockItemptr(fc_revmapbuf, fc_pagesPerRange, fc_heapBlk, fc_newtid);
		MarkBufferDirty(fc_revmapbuf);

		/* XLOG 相关内容 */
		if (RelationNeedsWAL(fc_idxrel))
		{
			xl_brin_update fc_xlrec;
			XLogRecPtr	fc_recptr;
			uint8		fc_info;

			fc_info = XLOG_BRIN_UPDATE | (fc_extended ? XLOG_BRIN_INIT_PAGE : 0);

			fc_xlrec.insert.offnum = fc_newoff;
			fc_xlrec.insert.heapBlk = fc_heapBlk;
			fc_xlrec.insert.pagesPerRange = fc_pagesPerRange;
			fc_xlrec.oldOffnum = fc_oldoff;

			XLogBeginInsert();

			/* 新页面 */
			XLogRegisterData((char *) &fc_xlrec, SizeOfBrinUpdate);

			XLogRegisterBuffer(0, fc_newbuf, REGBUF_STANDARD | (fc_extended ? REGBUF_WILL_INIT : 0));
			XLogRegisterBufData(0, (char *) unconstify(BrinTuple *, fc_newtup), fc_newsz);

			/* revmap 页面 */
			XLogRegisterBuffer(1, fc_revmapbuf, 0);

			/* 旧页面 */
			XLogRegisterBuffer(2, fc_oldbuf, REGBUF_STANDARD);

			fc_recptr = XLogInsert(RM_BRIN_ID, fc_info);

			PageSetLSN(fc_oldpage, fc_recptr);
			PageSetLSN(fc_newpage, fc_recptr);
			PageSetLSN(BufferGetPage(fc_revmapbuf), fc_recptr);
		}

		END_CRIT_SECTION();

		LockBuffer(fc_revmapbuf, BUFFER_LOCK_UNLOCK);
		LockBuffer(fc_oldbuf, BUFFER_LOCK_UNLOCK);
		UnlockReleaseBuffer(fc_newbuf);

		if (fc_extended)
		{
			RecordPageWithFreeSpace(fc_idxrel, fc_newblk, fc_freespace);
			FreeSpaceMapVacuumRange(fc_idxrel, fc_newblk, fc_newblk + 1);
		}

		return true;
	}
}

/*
 * 返回 brin_doupdate 是否可以进行同页更新。
 */
bool brin_can_do_samepage_update(Buffer fc_buffer, Size fc_origsz, Size fc_newsz)
{
	return
		((fc_newsz <= fc_origsz) ||
		 PageGetExactFreeSpace(BufferGetPage(fc_buffer)) >= (fc_newsz - fc_origsz));
}

/*
 * 将索引元组插入索引关系中。revmap 会被更新以标记包含给定页面的范围指向插入的条目。
 * 写入一个WAL记录。
 *
 * 如果缓冲区有效，首先检查是否有足够的空闲空间来插入新的条目；如果空间不足，将获取并固定一个新缓冲区。
 * 在插入时不必持有缓冲区锁，在退出时不持有缓冲区锁。
 *
 * 返回值是插入元组的偏移号。
 */
OffsetNumber brin_doinsert(Relation fc_idxrel, BlockNumber fc_pagesPerRange,
			  BrinRevmap *fc_revmap, Buffer *fc_buffer, BlockNumber fc_heapBlk,
			  BrinTuple *fc_tup, Size fc_itemsz)
{
	Page		fc_page;
	BlockNumber fc_blk;
	OffsetNumber fc_off;
	Size		fc_freespace = 0;
	Buffer		fc_revmapbuf;
	ItemPointerData fc_tid;
	bool		fc_extended;

	Assert(fc_itemsz == MAXALIGN(fc_itemsz));

	/* 如果项目过大，就别费心了。 */
	if (fc_itemsz > BrinMaxItemSize)
	{
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("index row size %zu exceeds maximum %zu for index \"%s\"",
						fc_itemsz, BrinMaxItemSize, RelationGetRelationName(fc_idxrel))));
		return InvalidOffsetNumber; /* 保持编译器安静 */
	}

	/* 确保 revmap 足够长以容纳我们需要的条目 */
	brinRevmapExtend(fc_revmap, fc_heapBlk);

	/*
	 * 如果有，获取调用者提供的缓冲区上的锁。如果没有足够的空间，解除固定以获取一个新的缓冲区。
	 */
	if (BufferIsValid(*fc_buffer))
	{
		/*
		 * 可能另一个后台（或者我们自己！）扩展了我们固定的页面上的 revmap，因此我们不能假设它仍然是一个常规页面。
		 */
		LockBuffer(*fc_buffer, BUFFER_LOCK_EXCLUSIVE);
		if (fc_br_page_get_freespace(BufferGetPage(*fc_buffer)) < fc_itemsz)
		{
			UnlockReleaseBuffer(*fc_buffer);
			*fc_buffer = InvalidBuffer;
		}
	}

	/*
	 * 如果我们仍然没有可用的缓冲区，让 brin_getinsertbuffer 为我们获取一个。
	 */
	if (!BufferIsValid(*fc_buffer))
	{
		do
			*fc_buffer = fc_brin_getinsertbuffer(fc_idxrel, InvalidBuffer, fc_itemsz, &fc_extended);
		while (!BufferIsValid(*fc_buffer));
	}
	else
		fc_extended = false;

	/* 现在获取 revmap 缓冲区的锁 */
	fc_revmapbuf = brinLockRevmapPageForUpdate(fc_revmap, fc_heapBlk);

	fc_page = BufferGetPage(*fc_buffer);
	fc_blk = BufferGetBlockNumber(*fc_buffer);

	/* 执行实际插入 */
	START_CRIT_SECTION();
	if (fc_extended)
		brin_page_init(fc_page, BRIN_PAGETYPE_REGULAR);
	fc_off = PageAddItem(fc_page, (Item) fc_tup, fc_itemsz, InvalidOffsetNumber,
					  false, false);
	if (fc_off == InvalidOffsetNumber)
		elog(ERROR, "failed to add BRIN tuple to new page");
	MarkBufferDirty(*fc_buffer);

	/* 需要更新下面的 FSM */
	if (fc_extended)
		fc_freespace = fc_br_page_get_freespace(fc_page);

	ItemPointerSet(&fc_tid, fc_blk, fc_off);
	brinSetHeapBlockItemptr(fc_revmapbuf, fc_pagesPerRange, fc_heapBlk, fc_tid);
	MarkBufferDirty(fc_revmapbuf);

	/* XLOG 相关内容 */
	if (RelationNeedsWAL(fc_idxrel))
	{
		xl_brin_insert fc_xlrec;
		XLogRecPtr	fc_recptr;
		uint8		fc_info;

		fc_info = XLOG_BRIN_INSERT | (fc_extended ? XLOG_BRIN_INIT_PAGE : 0);
		fc_xlrec.heapBlk = fc_heapBlk;
		fc_xlrec.pagesPerRange = fc_pagesPerRange;
		fc_xlrec.offnum = fc_off;

		XLogBeginInsert();
		XLogRegisterData((char *) &fc_xlrec, SizeOfBrinInsert);

		XLogRegisterBuffer(0, *fc_buffer, REGBUF_STANDARD | (fc_extended ? REGBUF_WILL_INIT : 0));
		XLogRegisterBufData(0, (char *) fc_tup, fc_itemsz);

		XLogRegisterBuffer(1, fc_revmapbuf, 0);

		fc_recptr = XLogInsert(RM_BRIN_ID, fc_info);

		PageSetLSN(fc_page, fc_recptr);
		PageSetLSN(BufferGetPage(fc_revmapbuf), fc_recptr);
	}

	END_CRIT_SECTION();

	/* 元组已稳固在缓冲区上；我们可以释放锁 */
	LockBuffer(*fc_buffer, BUFFER_LOCK_UNLOCK);
	LockBuffer(fc_revmapbuf, BUFFER_LOCK_UNLOCK);

	BRIN_elog((DEBUG2, "inserted tuple (%u,%u) for range starting at %u",
			   blk, off, heapBlk));

	if (fc_extended)
	{
		RecordPageWithFreeSpace(fc_idxrel, fc_blk, fc_freespace);
		FreeSpaceMapVacuumRange(fc_idxrel, fc_blk, fc_blk + 1);
	}

	return fc_off;
}

/*
 * 使用给定类型初始化页面。
 *
 * 调用者负责在适当的情况下标记为脏。
 */
void brin_page_init(Page fc_page, uint16 fc_type)
{
	PageInit(fc_page, BLCKSZ, sizeof(BrinSpecialSpace));

	BrinPageType(fc_page) = fc_type;
}

/*
 * 初始化新的 BRIN 索引的元页面。
 */
void brin_metapage_init(Page fc_page, BlockNumber fc_pagesPerRange, uint16 fc_version)
{
	BrinMetaPageData *fc_metadata;

	brin_page_init(fc_page, BRIN_PAGETYPE_META);

	fc_metadata = (BrinMetaPageData *) PageGetContents(fc_page);

	fc_metadata->brinMagic = BRIN_META_MAGIC;
	fc_metadata->brinVersion = fc_version;
	fc_metadata->pagesPerRange = fc_pagesPerRange;

	/*
	 * 注意这里我们稍微作弊。0 不是有效的 revmap 块号（因为它是元页面缓冲区），但这样做可以在索引创建时生成
	 * 第一个 revmap 页面。
	 */
	fc_metadata->lastRevmapPage = 0;

	/*
	 * 将 pd_lower 设置在元数据结束的后面。这是至关重要的，因为不这样做，如果 xlog.c 压缩页面，元数据将丢失。
	 */
	((PageHeader) fc_page)->pd_lower =
		((char *) fc_metadata + sizeof(BrinMetaPageData)) - (char *) fc_page;
}

/*
 * 启动页面疏散协议。
 *
 * 页面必须由调用者以独占模式锁定。
 *
 * 如果页面尚未初始化或为空，则返回 false而不执行任何操作；它可以在不进行任何进一步更改的情况下用于 revmap。
 * 如果它包含元组，则将其标记为疏散并返回 true。
 */
bool brin_start_evacuating_page(Relation fc_idxRel, Buffer fc_buf)
{
	OffsetNumber fc_off;
	OffsetNumber fc_maxoff;
	Page		fc_page;

	fc_page = BufferGetPage(fc_buf);

	if (PageIsNew(fc_page))
		return false;

	fc_maxoff = PageGetMaxOffsetNumber(fc_page);
	for (fc_off = FirstOffsetNumber; fc_off <= fc_maxoff; fc_off++)
	{
		ItemId		fc_lp;

		fc_lp = PageGetItemId(fc_page, fc_off);
		if (ItemIdIsUsed(fc_lp))
		{
			/*
			 * 防止其他后台向此页面添加更多内容：
			 * BRIN_EVACUATE_PAGE 通知 br_page_get_freespace 此页面不再可以用于添加新的元组。请注意，除了意外外，该标志
			 * 不会被 WAL 日志记录。
			 */
			BrinPageFlags(fc_page) |= BRIN_EVACUATE_PAGE;
			MarkBufferDirtyHint(fc_buf, true);

			return true;
		}
	}
	return false;
}

/*
 * 将所有元组移动出页面。
 *
 * 调用者必须持有页面上的锁。锁和固定将被释放。
 */
void brin_evacuate_page(Relation fc_idxRel, BlockNumber fc_pagesPerRange,
				   BrinRevmap *fc_revmap, Buffer fc_buf)
{
	OffsetNumber fc_off;
	OffsetNumber fc_maxoff;
	Page		fc_page;
	BrinTuple  *fc_btup = NULL;
	Size		fc_btupsz = 0;

	fc_page = BufferGetPage(fc_buf);

	Assert(BrinPageFlags(fc_page) & BRIN_EVACUATE_PAGE);

	fc_maxoff = PageGetMaxOffsetNumber(fc_page);
	for (fc_off = FirstOffsetNumber; fc_off <= fc_maxoff; fc_off++)
	{
		BrinTuple  *fc_tup;
		Size		fc_sz;
		ItemId		fc_lp;

		CHECK_FOR_INTERRUPTS();

		fc_lp = PageGetItemId(fc_page, fc_off);
		if (ItemIdIsUsed(fc_lp))
		{
			fc_sz = ItemIdGetLength(fc_lp);
			fc_tup = (BrinTuple *) PageGetItem(fc_page, fc_lp);
			fc_tup = brin_copy_tuple(fc_tup, fc_sz, fc_btup, &fc_btupsz);

			LockBuffer(fc_buf, BUFFER_LOCK_UNLOCK);

			if (!brin_doupdate(fc_idxRel, fc_pagesPerRange, fc_revmap, fc_tup->bt_blkno,
							   fc_buf, fc_off, fc_tup, fc_sz, fc_tup, fc_sz, false))
				fc_off--;			/* 重试 */

			LockBuffer(fc_buf, BUFFER_LOCK_SHARE);

			/* 可能有人在这个页面上扩展了 revmap */
			if (!BRIN_IS_REGULAR_PAGE(fc_page))
				break;
		}
	}

	UnlockReleaseBuffer(fc_buf);
}

/*
 * 给定一个BRIN索引页面，如果有必要，进行初始化，并记录其
 * 当前的空闲空间到FSM中。
 *
 * 这主要用于在清理时，当发现一个未初始化的页面时，这可能是
 * 由于关系扩展后发生崩溃，导致页面无法使用。
 *
 * 在这里，我们不打算更新上面的FSM页面，而是期待我们的
 * 调用者（brin_vacuum_scan）在扫描结束时修复它们。在
 * 本文件的其他地方，通常建议立即将新增的空闲空间传播到
 * 上面的FSM页面。
 */
void brin_page_cleanup(Relation fc_idxrel, Buffer fc_buf)
{
	Page		fc_page = BufferGetPage(fc_buf);

	/*
	 * 如果页面未初始化，现在进行初始化；同时在FSM中记录它。
	 *
	 * 其他人可能正在并发地扩展关系。为了避免在他们抓取
	 * 缓冲锁之前重新初始化该页面，我们稍微获取扩展锁。
	 * 由于他们在获取页面之前已经持有扩展锁，并且在初始化
	 * 之后仍然持有，我们可以确保看到他们的初始化。
	 */
	if (PageIsNew(fc_page))
	{
		LockRelationForExtension(fc_idxrel, ShareLock);
		UnlockRelationForExtension(fc_idxrel, ShareLock);

		LockBuffer(fc_buf, BUFFER_LOCK_EXCLUSIVE);
		if (PageIsNew(fc_page))
		{
			fc_brin_initialize_empty_new_buffer(fc_idxrel, fc_buf);
			LockBuffer(fc_buf, BUFFER_LOCK_UNLOCK);
			return;
		}
		LockBuffer(fc_buf, BUFFER_LOCK_UNLOCK);
	}

	/* 对于非规则索引页面，无需执行任何操作 */
	if (BRIN_IS_META_PAGE(BufferGetPage(fc_buf)) ||
		BRIN_IS_REVMAP_PAGE(BufferGetPage(fc_buf)))
		return;

	/* 测量空闲空间并记录 */
	RecordPageWithFreeSpace(fc_idxrel, BufferGetBlockNumber(fc_buf),
							fc_br_page_get_freespace(fc_page));
}

/*
 * 返回一个被锁定且独占的缓冲区，可以用于插入大小为
 * itemsz的索引项（调用者必须确保不请求无法满足的大小）。
 * 如果oldbuf是一个有效的缓冲区，它也会被锁定（按照避免
 * 死锁的顺序）。
 *
 * 如果我们发现旧页面不再是常规索引页面（由于revmap扩展），
 * 则解锁旧缓冲区并返回InvalidBuffer。
 *
 * 如果没有现有页面具有足够的空闲空间来容纳新项，则扩展
 * 关系。如果发生这种情况，*extended被设置为true，调用者
 * 有责任在使用之前初始化页面（并将该事实记录在WAL中）。
 * 调用者还应该在插入后更新FSM以反映页面剩余的空闲空间。
 *
 * 请注意，除非*extended被设置为true，否则不要求调用者更新FSM。
 * 这一政策意味着我们会在创建页面时更新FSM，以及在发现
 * 空间不足以插入所需元组时更新，但不会在每次向页面添加
 * 元组时都更新。
 *
 * 请注意，在某些特殊情况下，该例程有可能扩展关系然后不返回
 * 新页面。若发生这种情况，这个例程有责任将页面初始化记录在
 * WAL中，并在FSM中记录页面，因为调用者显然无法做到这一点。
 */
static Buffer fc_brin_getinsertbuffer(Relation fc_irel, Buffer fc_oldbuf, Size fc_itemsz,
					 bool *fc_extended)
{
	BlockNumber fc_oldblk;
	BlockNumber fc_newblk;
	Page		fc_page;
	Size		fc_freespace;

	/* 调用者必须已经检查 */
	Assert(fc_itemsz <= BrinMaxItemSize);

	if (BufferIsValid(fc_oldbuf))
		fc_oldblk = BufferGetBlockNumber(fc_oldbuf);
	else
		fc_oldblk = InvalidBlockNumber;

	/* 选择初始目标页面，如果已知则重用现有目标 */
	fc_newblk = RelationGetTargetBlock(fc_irel);
	if (fc_newblk == InvalidBlockNumber)
		fc_newblk = GetPageWithFreeSpace(fc_irel, fc_itemsz);

	/*
	 * 循环直到找到一个具有足够空闲空间的页面。到我们从这个
	 * 循环返回给调用者时，两个缓冲区都是有效的并且被锁定；
	 * 如果我们需要在此处重新开始，则两个页面都没有被锁定，
	 * 并且newblk也没有被固定（如果它甚至有效的话）。
	 */
	for (;;)
	{
		Buffer		fc_buf;
		bool		fc_extensionLockHeld = false;

		CHECK_FOR_INTERRUPTS();

		*fc_extended = false;

		if (fc_newblk == InvalidBlockNumber)
		{
			/*
			 * 根据FSM，现有索引页面中没有足够的空闲空间，
			 * 扩展关系以获得一个全新的页面。
			 */
			if (!RELATION_IS_LOCAL(fc_irel))
			{
				LockRelationForExtension(fc_irel, ExclusiveLock);
				fc_extensionLockHeld = true;
			}
			fc_buf = ReadBuffer(fc_irel, P_NEW);
			fc_newblk = BufferGetBlockNumber(fc_buf);
			*fc_extended = true;

			BRIN_elog((DEBUG2, "brin_getinsertbuffer: extending to page %u",
					   BufferGetBlockNumber(buf)));
		}
		else if (fc_newblk == fc_oldblk)
		{
			/*
			 * 这里有一个奇怪的边缘情况，FSM是过时的，
			 * 它给了我们旧页面。
			 */
			fc_buf = fc_oldbuf;
		}
		else
		{
			fc_buf = ReadBuffer(fc_irel, fc_newblk);
		}

		/*
		 * 我们首先锁定旧的缓冲区，如果它早于新的缓冲区；但是
		 * 然后我们需要检查它是否在此期间被转换为revmap页面。
		 * 如果我们检测到这种情况，放弃并告诉调用者重新开始。
		 */
		if (BufferIsValid(fc_oldbuf) && fc_oldblk < fc_newblk)
		{
			LockBuffer(fc_oldbuf, BUFFER_LOCK_EXCLUSIVE);
			if (!BRIN_IS_REGULAR_PAGE(BufferGetPage(fc_oldbuf)))
			{
				LockBuffer(fc_oldbuf, BUFFER_LOCK_UNLOCK);

				/*
				 * 新页面可能是通过扩展关系获得的。
				 * 在这种情况下，我们必须确保在离开之前
				 * 将其记录在FSM中，因为否则空间将永远丢失。
				 * 不过，我们不能让未初始化的页面进入FSM，
				 * 所以我们需要先对其进行初始化。
				 */
				if (*fc_extended)
					fc_brin_initialize_empty_new_buffer(fc_irel, fc_buf);

				if (fc_extensionLockHeld)
					UnlockRelationForExtension(fc_irel, ExclusiveLock);

				ReleaseBuffer(fc_buf);

				if (*fc_extended)
				{
					FreeSpaceMapVacuumRange(fc_irel, fc_newblk, fc_newblk + 1);
					/* 不应该影响，但不要混淆调用者 */
					*fc_extended = false;
				}

				return InvalidBuffer;
			}
		}

		LockBuffer(fc_buf, BUFFER_LOCK_EXCLUSIVE);

		if (fc_extensionLockHeld)
			UnlockRelationForExtension(fc_irel, ExclusiveLock);

		fc_page = BufferGetPage(fc_buf);

		/*
		 * 我们有一个新的缓冲区可以插入。检查新页面是否有
		 * 足够的空闲空间，如果有则返回；否则重新开始。
		 * （br_page_get_freespace还检查FSM是否没有给我们
		 * 一个已被重新用于revmap的页面。）
		 */
		fc_freespace = *fc_extended ?
			BrinMaxItemSize : fc_br_page_get_freespace(fc_page);
		if (fc_freespace >= fc_itemsz)
		{
			RelationSetTargetBlock(fc_irel, fc_newblk);

			/*
			 * 如果未锁定，则锁定旧的缓冲区。请注意，在这种情况下
			 * 我们可以确定它是一个常规页面：它的时间晚于
			 * 我们刚获得的新页面，而新页面不是revmap页面，
			 * revmap页面总是连续的。
			 */
			if (BufferIsValid(fc_oldbuf) && fc_oldblk > fc_newblk)
			{
				LockBuffer(fc_oldbuf, BUFFER_LOCK_EXCLUSIVE);
				Assert(BRIN_IS_REGULAR_PAGE(BufferGetPage(fc_oldbuf)));
			}

			return fc_buf;
		}

		/* 这个页面没有用。 */

		/*
		 * 如果全新的页面不包含足够的空闲空间供
		 * 新项使用，那么这个项目肯定是超大的。
		 * 大声抱怨；但首先确保我们初始化页面并将其记录为
		 * 空闲，以备下次使用。
		 */
		if (*fc_extended)
		{
			fc_brin_initialize_empty_new_buffer(fc_irel, fc_buf);
			/* 由于这不应该发生，跳过FreeSpaceMapVacuum */

			ereport(ERROR,
					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
					 errmsg("index row size %zu exceeds maximum %zu for index \"%s\"",
							fc_itemsz, fc_freespace, RelationGetRelationName(fc_irel))));
			return InvalidBuffer;	/* 保持编译器安静 */
		}

		if (fc_newblk != fc_oldblk)
			UnlockReleaseBuffer(fc_buf);
		if (BufferIsValid(fc_oldbuf) && fc_oldblk <= fc_newblk)
			LockBuffer(fc_oldbuf, BUFFER_LOCK_UNLOCK);

		/*
		 * 用新的、 presumably 更小的空闲空间值更新FSM，
		 * 然后搜索新的目标页面。
		 */
		fc_newblk = RecordAndGetPageWithFreeSpace(fc_irel, fc_newblk, fc_freespace, fc_itemsz);
	}
}

/*
 * 将页面初始化为一个空的常规BRIN页面，记录WAL日志，并在FSM中记录
 * 该页面。
 *
 * 在几种边角情况下，我们扩展关系以获得新页面，随后发现我们不能立即使用它。
 * 当这种情况发生时，我们不想让页面在FSM中未被记录，
 * 因为没有机制可以恢复空间，索引会膨胀。
 * 此外，由于我们不会WAL记录初始化页面的操作，
 * 页面将在备用模式（或恢复后）中未初始化。
 *
 * 虽然我们在这里将页面记录在FSM中，但调用者负责
 * 在适当的情况下进行FSM上层页面更新。
 */
static void fc_brin_initialize_empty_new_buffer(Relation fc_idxrel, Buffer fc_buffer)
{
	Page		fc_page;

	BRIN_elog((DEBUG2,
			   "brin_initialize_empty_new_buffer: initializing blank page %u",
			   BufferGetBlockNumber(buffer)));

	START_CRIT_SECTION();
	fc_page = BufferGetPage(fc_buffer);
	brin_page_init(fc_page, BRIN_PAGETYPE_REGULAR);
	MarkBufferDirty(fc_buffer);
	log_newpage_buffer(fc_buffer, true);
	END_CRIT_SECTION();

	/*
	 * 我们为此页面更新FSM，但这不会被WAL记录。
	 * 这是可以接受的，因为VACUUM将扫描索引并更新FSM
	 * 以处理在崩溃中遗忘的页面的FSM记录。
	 */
	RecordPageWithFreeSpace(fc_idxrel, BufferGetBlockNumber(fc_buffer),
							fc_br_page_get_freespace(fc_page));
}


/*
 * 返回常规 BRIN 索引页面上的自由空间量。
 *
 * 如果页面不是常规页面，或者已经被标记为
 * BRIN_EVACUATE_PAGE 标志，则返回 0。
 */
static Size fc_br_page_get_freespace(Page fc_page)
{
	if (!BRIN_IS_REGULAR_PAGE(fc_page) ||
		(BrinPageFlags(fc_page) & BRIN_EVACUATE_PAGE) != 0)
		return 0;
	else
		return PageGetFreeSpace(fc_page);
}
